function fairyDustCursor(options) {
  let possibleColors = (options && options.colors) || [
    '#D61C59',
    '#E7D84B',
    '#1B8798',
  ]
  let hasWrapperEl = options && options.element
  let element = hasWrapperEl || document.body

  let width = window.innerWidth
  let height = window.innerHeight
  const cursor = { x: width / 2, y: width / 2 }
  const lastPos = { x: width / 2, y: width / 2 }
  const particles = []
  const canvImages = []
  let canvas, context

  const char = '*'

  function init() {
    canvas = document.createElement('canvas')
    context = canvas.getContext('2d')
    canvas.style.top = '0px'
    canvas.style.left = '0px'
    canvas.style.pointerEvents = 'none'

    if (hasWrapperEl) {
      canvas.style.position = 'absolute'
      element.appendChild(canvas)
      canvas.width = element.clientWidth
      canvas.height = element.clientHeight
    } else {
      canvas.style.position = 'fixed'
      element.appendChild(canvas)
      canvas.width = width
      canvas.height = height
    }

    context.font = '21px serif'
    context.textBaseline = 'middle'
    context.textAlign = 'center'

    possibleColors.forEach((color) => {
      let measurements = context.measureText(char)
      let bgCanvas = document.createElement('canvas')
      let bgContext = bgCanvas.getContext('2d')

      bgCanvas.width = measurements.width
      bgCanvas.height =
        measurements.actualBoundingBoxAscent +
        measurements.actualBoundingBoxDescent

      bgContext.fillStyle = color
      bgContext.textAlign = 'center'
      bgContext.font = '21px serif'
      bgContext.textBaseline = 'middle'
      bgContext.fillText(
        char,
        bgCanvas.width / 2,
        measurements.actualBoundingBoxAscent
      )

      canvImages.push(bgCanvas)
    })

    bindEvents()
    loop()
  }

  // Bind events that are needed
  function bindEvents() {
    element.addEventListener('mousemove', onMouseMove)
    element.addEventListener('touchmove', onTouchMove, { passive: true })
    element.addEventListener('touchstart', onTouchMove, { passive: true })
    window.addEventListener('resize', onWindowResize)
  }

  function onWindowResize(e) {
    width = window.innerWidth
    height = window.innerHeight

    if (hasWrapperEl) {
      canvas.width = element.clientWidth
      canvas.height = element.clientHeight
    } else {
      canvas.width = width
      canvas.height = height
    }
  }

  function onTouchMove(e) {
    if (e.touches.length > 0) {
      for (let i = 0; i < e.touches.length; i++) {
        addParticle(
          e.touches[i].clientX,
          e.touches[i].clientY,
          canvImages[Math.floor(Math.random() * canvImages.length)]
        )
      }
    }
  }

  function onMouseMove(e) {
    window.requestAnimationFrame(() => {
      if (hasWrapperEl) {
        const boundingRect = element.getBoundingClientRect()
        cursor.x = e.clientX - boundingRect.left
        cursor.y = e.clientY - boundingRect.top
      } else {
        cursor.x = e.clientX
        cursor.y = e.clientY
      }

      const distBetweenPoints = Math.hypot(
        cursor.x - lastPos.x,
        cursor.y - lastPos.y
      )

      if (distBetweenPoints > 1.5) {
        addParticle(
          cursor.x,
          cursor.y,
          canvImages[Math.floor(Math.random() * possibleColors.length)]
        )

        lastPos.x = cursor.x
        lastPos.y = cursor.y
      }
    })
  }

  function addParticle(x, y, color) {
    particles.push(new Particle(x, y, color))
  }

  function updateParticles() {
    context.clearRect(0, 0, width, height)

    // Update
    for (let i = 0; i < particles.length; i++) {
      particles[i].update(context)
    }

    // Remove dead particles
    for (let i = particles.length - 1; i >= 0; i--) {
      if (particles[i].lifeSpan < 0) {
        particles.splice(i, 1)
      }
    }
  }

  function loop() {
    updateParticles()
    requestAnimationFrame(loop)
  }

  function Particle(x, y, canvasItem) {
    const lifeSpan = Math.floor(Math.random() * 30 + 60)
    this.initialLifeSpan = lifeSpan //
    this.lifeSpan = lifeSpan //ms
    this.velocity = {
      x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2),
      y: Math.random() * 0.7 + 0.9,
    }
    this.position = { x: x, y: y }
    this.canv = canvasItem

    this.update = function (context) {
      this.position.x += this.velocity.x
      this.position.y += this.velocity.y
      this.lifeSpan--

      this.velocity.y += 0.02

      const scale = Math.max(this.lifeSpan / this.initialLifeSpan, 0)

      context.drawImage(
        this.canv,
        this.position.x - (this.canv.width / 2) * scale,
        this.position.y - this.canv.height / 2,
        this.canv.width * scale,
        this.canv.height * scale
      )
    }
  }

  init()
}
new fairyDustCursor()
